// -*- mode:c++ -*-

// Copyright (c) 2015 Riscv Developers
// Copyright (c) 2016 The University of Virginia
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met: redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer;
// redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution;
// neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Declaration templates
def template AtomicMemOpDeclare {{
    /**
     * Static instruction class for an AtomicMemOp operation
     */
    class %(class_name)s : public %(base_class)s
    {
      public:
        // Constructor
        %(class_name)s(ExtMachInst machInst);

      protected:
        /*
         * The main RMW part of an AMO
         */
        class %(class_name)sRMW;
    };
}};

def template AtomicMemOpRMWDeclare {{
    /*
     * The main RMW part of an AMO
     */
    class %(class_name)s::%(class_name)sRMW : public %(base_class)s
    {
      private:
        %(reg_idx_arr_decl)s;

      public:
        // Constructor
        %(class_name)sRMW(ExtMachInst machInst, %(class_name)s *_p);

        Fault execute(ExecContext *, trace::InstRecord *) const override;
        Fault initiateAcc(ExecContext *, trace::InstRecord *) const override;
        Fault completeAcc(PacketPtr, ExecContext *,
                          trace::InstRecord *) const override;
    };
}};

def template LRSCDeclare {{
    /**
     * Static instruction class for an AtomicMemOp operation
     */
    class %(class_name)s : public %(base_class)s
    {
      public:
        // Constructor
        %(class_name)s(ExtMachInst machInst);

      protected:
        class %(class_name)sMicro;
    };
}};

def template LRSCMicroDeclare {{
    class %(class_name)s::%(class_name)sMicro : public %(base_class)s
    {
      private:
        %(reg_idx_arr_decl)s;

      public:
        // Constructor
        %(class_name)sMicro(ExtMachInst machInst, %(class_name)s *_p);

        Fault execute(ExecContext *, trace::InstRecord *) const override;
        Fault initiateAcc(ExecContext *, trace::InstRecord *) const override;
        Fault completeAcc(PacketPtr, ExecContext *,
                          trace::InstRecord *) const override;
    };
}};

// Constructor templates
def template LRSCMacroConstructor {{
    %(class_name)s::%(class_name)s(ExtMachInst machInst):
        %(base_class)s("%(mnemonic)s", machInst, %(op_class)s)
    {
        %(constructor)s;

        StaticInstPtr rel_fence;
        StaticInstPtr lrsc;
        StaticInstPtr acq_fence;

        // set up release fence
        if (RL) {
            rel_fence = new MemFenceMicro(machInst, No_OpClass);
            rel_fence->setFlag(IsFirstMicroop);
            rel_fence->setFlag(IsReadBarrier);
            rel_fence->setFlag(IsWriteBarrier);
            rel_fence->setFlag(IsDelayedCommit);
        }

        // set up atomic rmw op
        lrsc = new %(class_name)sMicro(machInst, this);

        if (!RL) {
            lrsc->setFlag(IsFirstMicroop);
        }

        if (!AQ) {
            lrsc->setFlag(IsLastMicroop);
        } else {
            lrsc->setFlag(IsDelayedCommit);
        }

        // set up acquire fence
        if (AQ) {
            acq_fence = new MemFenceMicro(machInst, No_OpClass);
            acq_fence->setFlag(IsLastMicroop);
            acq_fence->setFlag(IsReadBarrier);
            acq_fence->setFlag(IsWriteBarrier);
        }

        if (RL && AQ) {
            microops = {rel_fence, lrsc, acq_fence};
        } else if (RL) {
            microops = {rel_fence, lrsc};
        } else if (AQ) {
            microops = {lrsc, acq_fence};
        } else {
            microops = {lrsc};
        }
    }
}};

def template LRSCMicroConstructor {{
    %(class_name)s::%(class_name)sMicro::%(class_name)sMicro(
        ExtMachInst machInst, %(class_name)s *_p)
            : %(base_class)s("%(mnemonic)s", machInst, %(op_class)s)
    {
        %(set_reg_idx_arr)s;
        %(constructor)s;
    }
}};

def template AtomicMemOpMacroConstructor {{
    %(class_name)s::%(class_name)s(ExtMachInst machInst)
            : %(base_class)s("%(mnemonic)s", machInst, %(op_class)s)
    {
        %(constructor)s;

        StaticInstPtr rel_fence;
        StaticInstPtr rmw_op;
        StaticInstPtr acq_fence;

        // set up release fence
        if (RL) {
            rel_fence = new MemFenceMicro(machInst, No_OpClass);
            rel_fence->setFlag(IsFirstMicroop);
            rel_fence->setFlag(IsReadBarrier);
            rel_fence->setFlag(IsWriteBarrier);
            rel_fence->setFlag(IsDelayedCommit);
        }

        // set up atomic rmw op
        rmw_op = new %(class_name)sRMW(machInst, this);

        if (!RL) {
            rmw_op->setFlag(IsFirstMicroop);
        }

        if (!AQ) {
            rmw_op->setFlag(IsLastMicroop);
        } else {
            rmw_op->setFlag(IsDelayedCommit);
        }

        // set up acquire fence
        if (AQ) {
            acq_fence = new MemFenceMicro(machInst, No_OpClass);
            acq_fence->setFlag(IsLastMicroop);
            acq_fence->setFlag(IsReadBarrier);
            acq_fence->setFlag(IsWriteBarrier);
        }

        if (RL && AQ) {
            microops = {rel_fence, rmw_op, acq_fence};
        } else if (RL) {
            microops = {rel_fence, rmw_op};
        } else if (AQ) {
            microops = {rmw_op, acq_fence};
        } else {
            microops = {rmw_op};
        }
    }
}};

def template AtomicMemOpRMWConstructor {{
    %(class_name)s::%(class_name)sRMW::%(class_name)sRMW(
        ExtMachInst machInst, %(class_name)s *_p)
            : %(base_class)s("%(mnemonic)s[l]", machInst, %(op_class)s)
    {
        %(set_reg_idx_arr)s;
        %(constructor)s;

        // overwrite default flags
        flags[IsLoad] = false;
        flags[IsStore] = false;
        flags[IsAtomic] = true;
    }
}};

// execute() templates

def template LoadReservedExecute {{
    Fault
    %(class_name)s::%(class_name)sMicro::execute(
        ExecContext *xc, trace::InstRecord *traceData) const
    {
        Addr EA;

        %(op_decl)s;
        %(op_rd)s;
        %(ea_code)s;

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, LOAD_ADDR_MISALIGNED);
        }
        {
            Fault fault =
                readMemAtomicLE(xc, traceData, EA, Mem, memAccessFlags);
            if (fault != NoFault)
                return fault;
        }

        %(memacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

def template StoreCondExecute {{
    Fault %(class_name)s::%(class_name)sMicro::execute(ExecContext *xc,
        trace::InstRecord *traceData) const
    {
        Addr EA;
        uint64_t result;

        %(op_decl)s;
        %(op_rd)s;
        %(ea_code)s;

        %(memacc_code)s;

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, STORE_ADDR_MISALIGNED);
        }
        {
            Fault fault =
                writeMemAtomicLE(xc, traceData, Mem, EA, memAccessFlags,
                        &result);
            if (fault != NoFault)
                return fault;
        }
        // RISC-V has the opposite convention gem5 has for success flags,
        // so we invert the result here.
        result = !result;

        %(postacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

def template AtomicMemOpRMWExecute {{
    Fault %(class_name)s::%(class_name)sRMW::execute(ExecContext *xc,
        trace::InstRecord *traceData) const
    {
        Addr EA;

        %(op_decl)s;
        %(op_rd)s;
        %(ea_code)s;
        %(amoop_code)s;

        assert(amo_op);

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, AMO_ADDR_MISALIGNED);
        }
        {
            Fault fault =
                amoMemAtomicLE(xc, traceData, Mem, EA, memAccessFlags, amo_op);
            if (fault != NoFault)
                return fault;
        }

        %(memacc_code)s;
        %(postacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

// initiateAcc() templates

def template LoadReservedInitiateAcc {{
    Fault
    %(class_name)s::%(class_name)sMicro::initiateAcc(ExecContext *xc,
        trace::InstRecord *traceData) const
    {
        Addr EA;

        %(op_src_decl)s;
        %(op_rd)s;
        %(ea_code)s;

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, LOAD_ADDR_MISALIGNED);
        }
        return initiateMemRead(xc, traceData, EA, Mem, memAccessFlags);
    }
}};

def template StoreCondInitiateAcc {{
    Fault
    %(class_name)s::%(class_name)sMicro::initiateAcc(ExecContext *xc,
        trace::InstRecord *traceData) const
    {
        Addr EA;

        %(op_decl)s;
        %(op_rd)s;
        %(ea_code)s;
        %(memacc_code)s;

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, STORE_ADDR_MISALIGNED);
        }
        {
            Fault fault = writeMemTimingLE(xc, traceData, Mem, EA,
                memAccessFlags, nullptr);
            if (fault != NoFault)
                return fault;
        }

        %(op_wb)s;

        return NoFault;
    }
}};

def template AtomicMemOpRMWInitiateAcc {{
    Fault
    %(class_name)s::%(class_name)sRMW::initiateAcc(ExecContext *xc,
        trace::InstRecord *traceData) const
    {
        Addr EA;

        %(op_src_decl)s;
        %(op_rd)s;
        %(ea_code)s;
        %(amoop_code)s;

        assert(amo_op);

        if (!alignmentOk(xc, EA, sizeof(Mem))) {
            return std::make_shared<AddressFault>(EA, AMO_ADDR_MISALIGNED);
        }
        return initiateMemAMO(xc, traceData, EA, Mem, memAccessFlags, amo_op);
    }
}};

// completeAcc() templates

def template LoadReservedCompleteAcc {{
    Fault
    %(class_name)s::%(class_name)sMicro::completeAcc(PacketPtr pkt,
        ExecContext *xc, trace::InstRecord *traceData) const
    {
        %(op_decl)s;
        %(op_rd)s;

        getMemLE(pkt, Mem, traceData);

        %(memacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

def template StoreCondCompleteAcc {{
    Fault %(class_name)s::%(class_name)sMicro::completeAcc(Packet *pkt,
          ExecContext *xc, trace::InstRecord *traceData) const
    {
        %(op_dest_decl)s;

        // RISC-V has the opposite convention gem5 has for success flags,
        // so we invert the result here.
        uint64_t result = !pkt->req->getExtraData();

        %(postacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

def template AtomicMemOpRMWCompleteAcc {{
    Fault %(class_name)s::%(class_name)sRMW::completeAcc(Packet *pkt,
        ExecContext *xc, trace::InstRecord *traceData) const
    {
        %(op_decl)s;
        %(op_rd)s;

        getMemLE(pkt, Mem, traceData);

        %(memacc_code)s;
        %(op_wb)s;

        return NoFault;
    }
}};

// LR/SC/AMO decode formats

def format LoadReserved(memacc_code, postacc_code={{ }}, ea_code={{EA = Rs1;}},
        mem_flags=[], inst_flags=[]) {{
    macro_ea_code = ''
    macro_inst_flags = []
    macro_iop = InstObjParams(name, Name, 'LoadReserved', macro_ea_code,
                              macro_inst_flags)
    header_output = LRSCDeclare.subst(macro_iop)
    decoder_output = LRSCMacroConstructor.subst(macro_iop)
    decode_block = BasicDecode.subst(macro_iop)

    exec_output = ''

    mem_flags = makeList(mem_flags)
    inst_flags = makeList(inst_flags)
    iop = InstObjParams(name, Name, 'LoadReservedMicro',
        {'ea_code': ea_code, 'memacc_code': memacc_code,
        'postacc_code': postacc_code}, inst_flags)
    iop.constructor += '\n\tmemAccessFlags = memAccessFlags | ' + \
        '|'.join(['Request::%s' % flag for flag in mem_flags]) + ';'

    header_output += LRSCMicroDeclare.subst(iop)
    decoder_output += LRSCMicroConstructor.subst(iop)
    exec_output += LoadReservedExecute.subst(iop) \
        + LoadReservedInitiateAcc.subst(iop) \
        + LoadReservedCompleteAcc.subst(iop)
}};

def format StoreCond(memacc_code, postacc_code={{ }}, ea_code={{EA = Rs1;}},
        mem_flags=[], inst_flags=[]) {{
    macro_ea_code = ''
    macro_inst_flags = []
    macro_iop = InstObjParams(name, Name, 'StoreCond', macro_ea_code,
                              macro_inst_flags)
    header_output = LRSCDeclare.subst(macro_iop)
    decoder_output = LRSCMacroConstructor.subst(macro_iop)
    decode_block = BasicDecode.subst(macro_iop)

    exec_output = ''

    mem_flags = makeList(mem_flags)
    inst_flags = makeList(inst_flags)
    iop = InstObjParams(name, Name, 'StoreCondMicro',
        {'ea_code': ea_code, 'memacc_code': memacc_code,
        'postacc_code': postacc_code}, inst_flags)
    iop.constructor += '\n\tmemAccessFlags = memAccessFlags | ' + \
        '|'.join(['Request::%s' % flag for flag in mem_flags]) + ';'

    header_output += LRSCMicroDeclare.subst(iop)
    decoder_output += LRSCMicroConstructor.subst(iop)
    exec_output += StoreCondExecute.subst(iop) \
        + StoreCondInitiateAcc.subst(iop) \
        + StoreCondCompleteAcc.subst(iop)
}};

def format AtomicMemOp(memacc_code, amoop_code, postacc_code={{ }},
        ea_code={{EA = Rs1;}}, mem_flags=[], inst_flags=[]) {{
    macro_ea_code = ''
    macro_inst_flags = []
    macro_iop = InstObjParams(name, Name, 'AtomicMemOp', macro_ea_code,
                              macro_inst_flags)
    header_output = AtomicMemOpDeclare.subst(macro_iop)
    decoder_output = AtomicMemOpMacroConstructor.subst(macro_iop)
    decode_block = BasicDecode.subst(macro_iop)

    exec_output = ''

    rmw_mem_flags = makeList(mem_flags)
    rmw_inst_flags = makeList(inst_flags)
    rmw_iop = InstObjParams(name, Name, 'AtomicMemOpMicro',
                            {'ea_code': ea_code,
                             'memacc_code': memacc_code,
                             'postacc_code': postacc_code,
                             'amoop_code': amoop_code},
                            rmw_inst_flags)

    rmw_iop.constructor += '\n\tmemAccessFlags = memAccessFlags | ' + \
          '|'.join(['Request::%s' % flag for flag in rmw_mem_flags]) + ';'

    header_output += AtomicMemOpRMWDeclare.subst(rmw_iop)
    decoder_output += AtomicMemOpRMWConstructor.subst(rmw_iop)
    exec_output += AtomicMemOpRMWExecute.subst(rmw_iop) \
                 + AtomicMemOpRMWInitiateAcc.subst(rmw_iop) \
                 + AtomicMemOpRMWCompleteAcc.subst(rmw_iop)
}};
